﻿using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace CustomLibrary
{
    /// <summary>
    /// 按照步骤 1a 或 1b 操作，然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
    ///
    /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:CustomLibrary"
    ///
    ///
    /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:CustomLibrary;assembly=CustomLibrary"
    ///
    /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用，
    /// 并重新生成以避免编译错误:
    ///
    ///     在解决方案资源管理器中右击目标项目，然后依次单击
    ///     “添加引用”->“项目”->[浏览查找并选择此项目]
    ///
    ///
    /// 步骤 2)
    /// 继续操作并在 XAML 文件中使用控件。
    ///
    ///     <MyNamespace:SpNumber/>
    ///
    /// </summary>
    public class SpNumber : Control
    {
        /// <summary>
        /// The command that increases the value assigned to the numeric up-down control.
        /// </summary>
        public static RoutedCommand IncreaseCommand;

        /// <summary>
        /// The command that decreases the value assigned to the numeric up-down control.
        /// </summary>
        public static RoutedCommand DecreaseCommand;

        private TextBox _textBox;
        private string _lastInput;
        private decimal _inputValue;

        #region 依赖属性
        private const decimal DefaultMinimum = 0M;

        private const decimal DefaultMaximum = 100M;

        private const decimal DefaultIncrement = 1M;

        private const int DefaultDecimalPlaces = 0;

        public static readonly RoutedEvent ValueChangedEvent;

        #region Value
        public decimal Value
        {
            get { return (decimal)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(decimal), typeof(SpNumber), new PropertyMetadata(DefaultMinimum, OnValueChanged, CoerceValue));

        private static object CoerceValue(DependencyObject d, object baseValue)
        {
            decimal newValue = (decimal)baseValue;
            SpNumber control = (SpNumber)d;

            newValue = Math.Max(control.Minimum, Math.Min(control.Maximum, newValue));
            newValue = Decimal.Round(newValue, control.DecimalPlaces);

            return newValue;
        }

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SpNumber control = (SpNumber)d;

            decimal oldValue = (decimal)e.OldValue;
            decimal newValue = (decimal)e.NewValue;

            RoutedPropertyChangedEventArgs<decimal> arg = new RoutedPropertyChangedEventArgs<decimal>(
                oldValue, newValue, ValueChangedEvent);

            control.OnValueChanged(arg);
            control.UpdateText();
        }
        protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
        {
            RaiseEvent(args);
        }
        #endregion

        #region Incrment
        public decimal Incrment
        {
            get { return (decimal)GetValue(IncrmentProperty); }
            set { SetValue(IncrmentProperty, value); }
        }
        public static readonly DependencyProperty IncrmentProperty =
            DependencyProperty.Register("Incrment", typeof(decimal), typeof(SpNumber), new PropertyMetadata(DefaultIncrement, OnIncrementChanged, CoerceIncrement), ValidateIncrement);
        private static void OnIncrementChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
        }
        private static object CoerceIncrement(DependencyObject element, object value)
        {
            decimal newIncrement = (decimal)value;
            var control = (SpNumber)element;

            decimal coercedNewIncrement = Decimal.Round(newIncrement, control.DecimalPlaces);

            if (coercedNewIncrement == Decimal.Zero)
            {
                coercedNewIncrement = SmallestForDecimalPlaces(control.DecimalPlaces);
            }

            return coercedNewIncrement;
        }
        private static decimal SmallestForDecimalPlaces(int decimalPlaces)
        {
            if (decimalPlaces < 0)
                throw new ArgumentOutOfRangeException("decimalPlaces");

            decimal d = 1;

            for (int i = 0; i < decimalPlaces; i++)
            {
                d /= 10;
            }

            return d;
        }
        private static bool ValidateIncrement(object value)
        {
            decimal change = (decimal)value;
            return change > 0;
        }
        #endregion

        #region Maximum
        public decimal Maximum
        {
            get { return (decimal)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }

        public static readonly DependencyProperty MaximumProperty =
            DependencyProperty.Register("Maximum", typeof(decimal), typeof(SpNumber), new PropertyMetadata(DefaultMaximum, OnMaximumChanged, CoerceMaximum), ValidateIncrement);
        private static void OnMaximumChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
            element.CoerceValue(ValueProperty);
        }
        private static object CoerceMaximum(DependencyObject element, object value)
        {
            var control = (SpNumber)element;
            decimal newMaximum = (decimal)value;
            return Decimal.Round(Math.Max(newMaximum, control.Minimum), control.DecimalPlaces);
        }
        #endregion

        #region Minimum
        public decimal Minimum
        {
            get { return (decimal)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }

        public static readonly DependencyProperty MinimumProperty =
            DependencyProperty.Register("Minimum", typeof(decimal), typeof(SpNumber), new PropertyMetadata(DefaultMinimum, OnMinimumChanged, CoerceMinimum));
        private static void OnMinimumChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
            element.CoerceValue(MaximumProperty);
            element.CoerceValue(ValueProperty);
        }
        private static object CoerceMinimum(DependencyObject element, object value)
        {
            decimal newMinimum = (decimal)value;
            var control = (SpNumber)element;
            return Decimal.Round(newMinimum, control.DecimalPlaces);
        }
        #endregion

        #region DecimalPlaces

        /// <summary>
        /// 小数点
        /// </summary>
        public int DecimalPlaces
        {
            get { return (int)GetValue(DecimalPlacesProperty); }
            set { SetValue(DecimalPlacesProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DecimalPlaces.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DecimalPlacesProperty =
            DependencyProperty.Register("DecimalPlaces", typeof(int), typeof(SpNumber), new PropertyMetadata(DefaultDecimalPlaces, OnDecimalPlacesChanged), ValidateDecimalPlaces);

        private static void OnDecimalPlacesChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
            SpNumber control = (SpNumber)element;

            control.CoerceValue(IncrmentProperty);
            control.CoerceValue(MinimumProperty);
            control.CoerceValue(MaximumProperty);
            control.CoerceValue(ValueProperty);

            control.UpdateText();
        }
        private static bool ValidateDecimalPlaces(object value)
        {
            int decimalPlaces = (int)value;
            return decimalPlaces >= 0;
        }
        #endregion

        #region NumberFormatInfo


        public NumberFormatInfo NumberFormatInfo
        {
            get { return (NumberFormatInfo)GetValue(NumberFormatInfoProperty); }
            set { SetValue(NumberFormatInfoProperty, value); }
        }

        // Using a DependencyProperty as the backing store for NumberFormatInfo.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NumberFormatInfoProperty =
            DependencyProperty.Register("NumberFormatInfo", typeof(NumberFormatInfo), typeof(SpNumber), new PropertyMetadata(NumberFormatInfo.CurrentInfo.Clone(), OnNumberFormatInfoChanged));
        private static void OnNumberFormatInfoChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
            var control = element as SpNumber;
            control.UpdateText();
        }

        #endregion

        #region 其他

        public bool InterceptArrowKeys
        {
            get { return (bool)GetValue(InterceptArrowKeysProperty); }
            set { SetValue(InterceptArrowKeysProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InterceptArrowKeys.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InterceptArrowKeysProperty =
            DependencyProperty.Register("InterceptArrowKeys", typeof(bool), typeof(SpNumber), new PropertyMetadata(true));


        #endregion

        public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }

            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        #endregion
        static SpNumber()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SpNumber), new FrameworkPropertyMetadata(typeof(SpNumber)));

            IncreaseCommand = new RoutedCommand("IncreaseCommand", typeof(SpNumber));
            CommandManager.RegisterClassCommandBinding(typeof(SpNumber), new CommandBinding(IncreaseCommand, OnIncreaseCommand));
            CommandManager.RegisterClassInputBinding(typeof(SpNumber), new InputBinding(IncreaseCommand, new KeyGesture(Key.Up)));

            DecreaseCommand = new RoutedCommand("DecreaseCommand", typeof(SpNumber));
            CommandManager.RegisterClassCommandBinding(typeof(SpNumber), new CommandBinding(DecreaseCommand, OnDecreaseCommand));
            CommandManager.RegisterClassInputBinding(typeof(SpNumber), new InputBinding(DecreaseCommand, new KeyGesture(Key.Down)));

            ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble,
           typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(SpNumber));

            EventManager.RegisterClassHandler(typeof(SpNumber),
            Mouse.MouseDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown), true);
        }
        private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var control = (SpNumber)sender;

            // When someone click on a part in the NumericUpDown and it's not focusable
            // NumericUpDown needs to take the focus in order to process keyboard correctly
            if (!control.IsKeyboardFocusWithin)
            {
                e.Handled = control.Focus() || e.Handled;
            }
        }
        #region 重写
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            base.OnMouseWheel(e);

            if (IsKeyboardFocusWithin)
            {
                if (e.Delta > 0)
                {
                    OnIncrease();
                }
                else
                {
                    OnDecrease();
                }
            }
        }
        protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                OnGotFocus();
            }
            else
            {
                OnLostFocus();
            }
        }
        private void OnGotFocus()
        {
            if (_textBox != null)
            {
                _textBox.Focus();
            }

            UpdateText();
        }

        private void OnLostFocus()
        {
            UpdateValue();
            UpdateText();
        }
        #endregion

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (_textBox != null)
            {
                _textBox.TextChanged -= new TextChangedEventHandler(OnTextBoxTextChanged);
                _textBox.PreviewKeyDown -= new KeyEventHandler(OnTextBoxPreviewKeyDown);
            }

            _textBox = (TextBox)base.GetTemplateChild("textbox");

            if (_textBox != null)
            {
                _textBox.TextChanged += new TextChangedEventHandler(OnTextBoxTextChanged);
                _textBox.PreviewKeyDown += new KeyEventHandler(OnTextBoxPreviewKeyDown);
                _textBox.IsReadOnly = false;
            }

            UpdateText();
        }
     
        private void UpdateText()
        {
            NumberFormatInfo formatInfo = (NumberFormatInfo)NumberFormatInfo.Clone();
            formatInfo.NumberGroupSeparator = String.Empty;

            string formattedValue = Value.ToString("F" + DecimalPlaces, formatInfo);

            if (_textBox != null)
            {
                _lastInput = formattedValue;
                _textBox.Text = formattedValue;
            }
        }

        private void OnTextBoxPreviewKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Up:
                    if (InterceptArrowKeys)
                        OnIncrease();
                    break;

                case Key.Down:
                    if (InterceptArrowKeys)
                        OnDecrease();
                    break;

                case Key.Return:
                    UpdateValue();
                    UpdateText();
                    break;

                default:
                    return;
            }

            e.Handled = true;
        }
        protected virtual void OnIncrease()
        {
            UpdateValue();

            if (Value + Incrment <= Maximum)
            {
                Value += Incrment;
            }
        }

        /// <summary>
        /// DecreaseCommand event handler.
        /// </summary>
        protected virtual void OnDecrease()
        {
            UpdateValue();

            if (Value - Incrment >= Minimum)
            {
                Value -= Incrment;
            }
        }
        internal void UpdateValue()
        {
            if (_inputValue != Value)
            {
                Value = (decimal)CoerceValue(this, _inputValue);
            }
        }
        private const NumberStyles NumberStyle = NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
        private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
        {
            string text = _textBox.Text;

            if (String.IsNullOrEmpty(text) || text == NumberFormatInfo.NegativeSign)
            {
                return;
            }

            decimal parsedValue = 0M;
            if (decimal.TryParse(text, NumberStyle, NumberFormatInfo, out parsedValue))
            {
                if ((DecimalPlaces == 0) && (text.Contains(NumberFormatInfo.NumberDecimalSeparator)))
                {
                    ReturnPreviousInput();
                    return;
                }

                _lastInput = text;
                _inputValue = parsedValue;
                return;
            }

            ReturnPreviousInput();
        }

        private void ReturnPreviousInput()
        {
            int selectionLenght = _textBox.SelectionLength;
            int selectionStart = _textBox.SelectionStart;

            _textBox.Text = _lastInput;
            _textBox.SelectionStart = (selectionStart == 0) ? 0 : (selectionStart - 1);
            _textBox.SelectionLength = selectionLenght;
        }

        private static void OnDecreaseCommand(object sender, ExecutedRoutedEventArgs e)
        {
            var control = sender as SpNumber;

            if (control != null)
            {
                control.OnDecrease();
            }
        }

        private static void OnIncreaseCommand(object sender, ExecutedRoutedEventArgs e)
        {
            var control = sender as SpNumber;

            if (control != null)
            {
                control.OnIncrease();
            }
        }
    }
}
